package com.shareppy.norman.core.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.shareppy.norman.core.annotations.Inherited;
import com.shareppy.norman.core.db.CursorWrapper;
import com.shareppy.norman.core.entity.AbstractEntity;
import com.shareppy.utils.CollectionsUtil;
import com.shareppy.utils.ReflectionUtils;

public class EntityUtils {


	private EntityUtils() {
		super();
	}

	/**
	 * Obtiene el nombre de una tabla usando como fuente su clase de entidad
	 * 
	 * @param entity
	 * @return
	 */
	public static String getTableName(AbstractEntity entity) {
		if (entity == null) {
			return null;
		}
		return getTableName(entity.getClass());
	}
	
	/**
	 * Obtiene el nombre de una tabla usando como fuente su clase de entidad
	 * 
	 * @param entity
	 * @return
	 */
	public static String getTableName(Class<? extends AbstractEntity> entityclass) {
		if (entityclass == null) {
			return null;
		}
		return entityclass.getSimpleName();
	}

	/**
	 * Transforma una entidad y sus campos a un objeto de tipo ContentValues
	 * para serializar a SQLite
	 * 
	 * @param entity
	 *            La entidad a serializar
	 * @return
	 * @throws Exception
	 */
	public static <T extends AbstractEntity> Map<String, Object> getContentValues(
			T entity) throws Exception {
		if (entity == null) {
			return null;
		}
		Map<String, Object> cv = new LinkedHashMap<String, Object>();
		if (EntityUtils.isEdit(entity)) {
			cv.put(AbstractEntity.ID_COLUMN, entity.getId());
		}
		Field[] fields = ReflectionUtils.getFields(entity, Inherited.class);
		for (Field field : fields) {
			field.setAccessible(true);
			if (ReflectionUtils.isInt(field)) {
				cv.put(field.getName(), field.getInt(entity));
			} else if (ReflectionUtils.isLong(field)) {
				cv.put(field.getName(), field.getLong(entity));
			} else if (ReflectionUtils.isFloat(field)) {
				cv.put(field.getName(), field.getFloat(entity));
			} else if (ReflectionUtils.isDouble(field)) {
				cv.put(field.getName(), field.getDouble(entity));
			} else if (ReflectionUtils.isText(field)) {
				cv.put(field.getName(), (String) field.get(entity));
			} else {
				throw new Exception(field.getType().getName()
						.concat(" is not supported."));
			}
		}
		return cv;
	}

	public static <T extends AbstractEntity> boolean isCreate(T entity) {
		return entity.getId() <= 0;
	}

	public static <T extends AbstractEntity> boolean isEdit(T entity) {
		return !isCreate(entity);
	}

	public static <T extends AbstractEntity> String[] getColumnArray(
			String[] explicitColumns, T entity) {
		return getColumnArray(explicitColumns, entity.getClass());
	}
	
	public static <T extends AbstractEntity> String[] getColumnArray(
			String[] explicitColumns, Class<T> entity) {
		if (explicitColumns == null) {
			List<String> columns = getColumns(entity);
			return columns.toArray(new String[columns.size()]);
		}

		if (explicitColumns.length == 1) {
			if (explicitColumns[0] == null
					|| "".equals(explicitColumns[0].trim())) {
				List<String> columns = getColumns(entity);
				return columns.toArray(new String[columns.size()]);
			}
		}

		return explicitColumns;
	}

	public static <T extends AbstractEntity> List<String> getColumns(T entity) {
		return getColumns(entity.getClass());
	}
	
	public static <T extends AbstractEntity> List<String> getColumns(Class<T> entityClass) {
		List<String> columns = new ArrayList<String>();
		columns.add(AbstractEntity.ID_COLUMN);
		Field[] fields = ReflectionUtils.getFields(entityClass, Inherited.class);
		for (Field field : fields) {
			field.setAccessible(true);
			columns.add(field.getName());
		}
		return columns;
	}

	public static <T extends AbstractEntity> T cursor2Entity(T entity,
			String[] columns, CursorWrapper<?> cursor) throws Exception {
		Class<? extends AbstractEntity> entityClass = entity.getClass();
		List<String> columnList = Arrays.asList(columns);
		boolean hasId = columnList.contains(AbstractEntity.ID_COLUMN);
		int index = 0;
		if (hasId) {
			entity.setId(cursor.getLong(index));
			index++;
		}
		for (String column : columnList) {
			String plainColumn = cleanColumn(column);
			if (AbstractEntity.ID_COLUMN.equals(plainColumn)) {
				continue;
			}
			Field field = ReflectionUtils.getField(entityClass, plainColumn, Inherited.class);
			field.setAccessible(true);
			if (ReflectionUtils.isInt(field)) {
				field.setInt(entity, cursor.getInt(index));
			} else if (ReflectionUtils.isLong(field)) {
				field.setLong(entity, cursor.getLong(index));
			} else if (ReflectionUtils.isFloat(field)) {
				field.setFloat(entity, cursor.getFloat(index));
			} else if (ReflectionUtils.isDouble(field)) {
				field.setDouble(entity, cursor.getDouble(index));
			} else if (ReflectionUtils.isText(field)) {
				field.set(entity, cursor.getString(index));
			}
			index++;
		}
		return entity;
	}

	public static <T extends AbstractEntity> T toEntity(T entity,
			Map<String, Object> cv, Class<? extends Annotation> superClassAnnotation) throws Exception {
		Class<? extends AbstractEntity> entityClass = entity.getClass();
		Set<String> keySet = cv.keySet();
		for (String columnName : keySet) {
			if( AbstractEntity.ID_COLUMN.equals( columnName ) ){
				entity.setId( (Long)cv.get(columnName) );
				continue;
			}
			Field field = ReflectionUtils.getField(entityClass, columnName, superClassAnnotation);
			field.setAccessible(true);
			if (ReflectionUtils.isInt(field)) {
				field.setInt(entity, (Integer)cv.get(columnName));
			} else if (ReflectionUtils.isLong(field)) {
				field.setLong(entity, (Long)cv.get(columnName));
			} else if (ReflectionUtils.isFloat(field)) {
				field.setFloat(entity, (Float)cv.get(columnName));
			} else if (ReflectionUtils.isDouble(field)) {
				field.setDouble(entity, (Double)cv.get(columnName));
			} else if (ReflectionUtils.isText(field)) {
				field.set(entity, (String)cv.get(columnName));
			}
		}
		return entity;
	}

	public static String cleanColumn(String column) {
		return column.replace("MAX(", "").replace("MIN(", "")
				.replace("SUM(", "").replace("COUNT(", "").replace("AVG(", "")
				.replace(")", "");
	}

	public static String getDBtype(Field field) throws Exception {
		if (ReflectionUtils.isInteger(field)) {
			return "INTEGER";
		}
		if (ReflectionUtils.isReal(field)) {
			return "REAL";
		}
		if (ReflectionUtils.isText(field)) {
			return "TEXT";
		}
		throw new Exception("Type not supported");
	}

	public static String parseConditions(String[] conditions) {
		if (conditions == null) {
			return null;
		}
		if (conditions.length == 1 && "".equals(conditions[0].trim())) {
			return null;
		}
		StringBuilder selection = new StringBuilder();
		for (int i = 0; i < conditions.length; i += 2) {
			if (i > 0) {
				selection.append(" AND ");
			}
			selection.append(conditions[i]).append(conditions[i + 1])
					.append("?");
		}
		return selection.toString();
	}

	public static <T extends AbstractEntity> String[] getConditionParams(
			T entity, String[] conditions, Class<? extends Annotation> superClassAnnotation) throws NoSuchFieldException,
			IllegalArgumentException, IllegalAccessException {
		if (conditions == null) {
			return null;
		}
		if (conditions.length == 1 && "".equals(conditions[0].trim())) {
			return null;
		}
		String[] params = new String[conditions.length / 2];
		Class<? extends AbstractEntity> entityClass = entity.getClass();
		for (int i = 0; i < conditions.length; i += 2) {
			Field field = ReflectionUtils.getField(entityClass, conditions[i], superClassAnnotation);
			field.setAccessible(true);
			if (ReflectionUtils.isInt(field)) {
				params[i / 2] = String.valueOf(field.getInt(entity));
			} else if (ReflectionUtils.isLong(field)) {
				params[i / 2] = String.valueOf(field.getLong(entity));
			} else if (ReflectionUtils.isFloat(field)) {
				params[i / 2] = String.valueOf(field.getFloat(entity));
			} else if (ReflectionUtils.isDouble(field)) {
				params[i / 2] = String.valueOf(field.getDouble(entity));
			} else if (ReflectionUtils.isText(field)) {
				params[i / 2] = String.valueOf((String) field.get(entity));
			}
		}
		return params;
	}

	public static <T extends AbstractEntity> String getGroupByClause(
			String[] groupBy) {
		if (groupBy == null) {
			return null;
		}
		if (groupBy.length == 1) {
			if (groupBy[0] == null || "".equals(groupBy[0].trim())) {
				return null;
			}
			return groupBy[0];
		}
		return CollectionsUtil.join(Arrays.asList(groupBy), ',');
	}

	public static <T extends AbstractEntity> List<T> cursorToList(T entity,
			String[] columns, CursorWrapper<?> cursor) throws InstantiationException,
			IllegalAccessException, Exception {
		cursor.moveToFirst();
		List<T> list = new ArrayList<T>();
		entity = ReflectionUtils.newInstance(entity);
		while (!cursor.isAfterLast()) {
			list.add(EntityUtils.cursor2Entity(entity, columns, cursor));
			cursor.moveToNext();
			entity = ReflectionUtils.newInstance(entity);
		}
		cursor.close();
		return list;
	}

	public static List<Map<String, Object>> cursorToContentValues(CursorWrapper<?> cursor) {
		if (cursor == null) {
			return null;
		}
		List<Map<String, Object>> values = new ArrayList<Map<String, Object>>();
		cursor.moveToFirst();
		int columnCount = cursor.getColumnCount();
		while (!cursor.isAfterLast()) {
			Map<String, Object> cv = new LinkedHashMap<String, Object>();
			for (int i = 0; i < columnCount; i++) {
				int type = cursor.getType(i);
				if (type == CursorWrapper.FIELD_TYPE_INTEGER) {
					cv.put(cursor.getColumnName(i), cursor.getLong(i));
				} else if (type == CursorWrapper.FIELD_TYPE_FLOAT) {
					cv.put(cursor.getColumnName(i), cursor.getDouble(i));
				} else if (type == CursorWrapper.FIELD_TYPE_STRING) {
					cv.put(cursor.getColumnName(i), cursor.getString(i));
				}
			}
			values.add(cv);
			cursor.moveToNext();
		}
		cursor.close();
		return values;
	}

	public static <AE extends AbstractEntity> List<Long> getIds(List<AE> list) {
		if (list == null) {
			return null;
		}
		List<Long> ids = new ArrayList<Long>();
		for (AE entity : list) {
			ids.add(entity.getId());
		}
		return ids;
	}
	
	public static <T extends AbstractEntity> T contentValues2Entity(T entity,
			Map<String, Object> cv) throws Exception {
		Class<? extends AbstractEntity> entityClass = entity.getClass();
		Set<String> keySet = cv.keySet();
		for (String columnName : keySet) {
			if( AbstractEntity.ID_COLUMN.equals( columnName ) ){
				entity.setId( (Long)cv.get(columnName) );
				continue;
			}
			Field field = ReflectionUtils.getField(entityClass, columnName, Inherited.class);
			field.setAccessible(true);
			if (ReflectionUtils.isInt(field)) {
				field.setInt(entity, (Integer)cv.get(columnName));
			} else if (ReflectionUtils.isLong(field)) {
				field.setLong(entity, (Long)cv.get(columnName));
			} else if (ReflectionUtils.isFloat(field)) {
				field.setFloat(entity, (Float)cv.get(columnName));
			} else if (ReflectionUtils.isDouble(field)) {
				field.setDouble(entity, (Double)cv.get(columnName));
			} else if (ReflectionUtils.isText(field)) {
				field.set(entity, (String)cv.get(columnName));
			}
		}
		return entity;
	}
}